package space.edge.d.demo.qrcode.privatecode.detector;

import android.graphics.Point;
import android.util.Log;

import space.edge.d.demo.qrcode.common.BitMatrix;
import space.edge.d.demo.qrcode.common.ResultPointCallback;
import space.edge.d.demo.qrcode.exception.NotFoundException;

import java.util.LinkedList;
import java.util.Queue;

/**
 * Created by gsl on 2017/9/15.
 * 寻找辅助定位符
 */

final class AlignmentPatternFinder {

    private BitMatrix mImage;
    private int startX, startY, width, height;
    private int oriCenterX, oriCenterY;
    private ResultPointCallback mCallback;

    AlignmentPatternFinder(BitMatrix img, int startX, int startY, int width, int height, int oriCenterX, int oriCenterY, ResultPointCallback callback) {
        mImage = img;
        this.startX = startX;
        this.startY = startY;
        this.width = width;
        this.height = height;
        this.oriCenterX = oriCenterX;
        this.oriCenterY = oriCenterY;
        mCallback = callback;
    }

    /**
     * 1.首先判断初始中心点是否在搜索范围内，不在范围内直接返回
     * 2.否则从初始中心点开始遍历
     * 3.如果初始中心点是白像素，则向上遍历，直到遇到黑像素然后又一次遇到白像素
     * 4.这时候开始
     * <p>
     * 上面的方式否定
     * <p>
     * 应该把辅助定位符中间的白色像素重置为黑色，重置的方法是：首先要找到一个白像素，
     * 然后通过查找附近四个或八个连通的像素点，如果为白则置为黑，然后递归找，直到找不到白像素则结束
     * 如何找到第一个白像素呢？
     * 1.初始中心点为白像素
     * 2.初始中心点为黑像素
     */
    AlignmentPattern find() throws NotFoundException {
        if (oriCenterX > mImage.getWidth() || oriCenterX < 0 || oriCenterY > mImage.getHeight() || oriCenterY < 0 || width <= 0 || height <= 0)
            throw NotFoundException.getNotFoundInstance();
        boolean ori = mImage.get(oriCenterX, oriCenterY);
        BitMatrix matrix = new BitMatrix(width, height);
        Point point;
        if (!ori) {
            // 这里可以优化为找到一个黑点，然后执行copyAlignmentPatternToNewMatrix（）方法
            point = findABlackBlock();
            if (point == null)
                return null;
        } else {
            point = new Point(oriCenterX, oriCenterY);
        }
        copyAlignmentPatternToNewMatrix(point.x, point.y, matrix);
        Log.d("gsl", matrix.toString());
        // 这一步应该是多余的，可以不用重置白像素直接计算出边界黑点
        // 将初始中心点换算到copy矩阵中
        // 然后对于x轴坐标，横向从0开始扫描，找到第y行第一个黑点，再横向从末尾开始扫描，找到最后一个黑点
        // 然后对于y轴坐标，竖向从0开始扫描，找到第x列第一个黑点，在竖向从末尾开始扫描，找到最后一个黑点
        Point realCenter = findCenter(matrix);
        if (realCenter == null)
            throw NotFoundException.getNotFoundInstance();
        AlignmentPattern pattern = new AlignmentPattern(realCenter.x + startX, realCenter.y + startY, 0);
        if (mCallback != null)
            mCallback.foundPossibleResultPoint(pattern);
        return pattern;
    }

    private Point findCenter(BitMatrix bitMatrix) {
        Point realCenter = new Point();

        int startX = -1, endX = -1, startY = -1, endY = -1;

        int y = oriCenterY - this.startY;
        for (int i = 0; i < bitMatrix.getWidth(); i++) {
            if (bitMatrix.get(i, y)) {
                startX = i;
                break;
            }
        }

        for (int i = bitMatrix.getWidth() - 1; i >= 0; i--) {
            if (bitMatrix.get(i, y)) {
                endX = i;
                break;
            }
        }

        int x = oriCenterX - this.startX;
        for (int i = 0; i < bitMatrix.getHeight(); i++) {
            if (bitMatrix.get(x, i)) {
                startY = i;
                break;
            }
        }

        for (int i = bitMatrix.getHeight() - 1; i >= 0; i--) {
            if (bitMatrix.get(x, i)) {
                endY = i;
                break;
            }
        }

        if (startX >= endX || endY <= startY || startX == -1 || endX == -1 || startY == -1 || endY == -1)
            return null;
        int realX = (endX + startX) / 2;
        int realY = (endY + startY) / 2;
        realCenter.set(realX, realY);
        return realCenter;
    }

    private Point findABlackBlock() {
        int x = 0;
        for (int i = oriCenterX + 1; i < startX + width; i++) {
            if (mImage.get(i, oriCenterY)) {
                x = i;
                break;
            }
        }
        if (x == 0)
            return null;
        Point center = new Point();
        center.set(x, oriCenterY);
        return center;
    }

    // FIXME: 2017/10/31 去掉日志之后，耗时短了很多，
    // FIXME：也可以换一种算法去寻找中心点，可以使用寻找轮廓的方式去找到外轮廓，进而找到真正的中心点
    private void copyAlignmentPatternToNewMatrix(int x, int y, BitMatrix newBitMatrix) {

        boolean curr = mImage.get(x, y);
        if (!curr) {
            return;
        }
        Queue<Point> queue = new LinkedList<>();
        queue.offer(new Point(x, y));
        Point point;
        long start = System.currentTimeMillis();
        while ((point = queue.poll()) != null) {
            int newX = point.x - startX;
            int newY = point.y - startY;
            newBitMatrix.set(newX, newY);
            int startX = Math.max(this.startX, point.x - 1);
            int endX = Math.min(point.x + 1, this.startX + width);
            int startY = Math.max(this.startY, point.y - 1);
            int endY = Math.min(point.y + 1, this.startY + height);
            for (int i = startX; i <= endX; i++) {
                for (int j = startY; j <= endY; j++) {
                    if (i == newX && j == newY)
                        continue;
                    int newI = i - this.startX;
                    int newJ = j - this.startY;
                    if (newI >= newBitMatrix.getWidth() || newJ >= newBitMatrix.getHeight() || newI < 0 || newJ < 0)
                        continue;
                    if (mImage.get(i, j) && !newBitMatrix.get(i - this.startX, j - this.startY)) {
                        mImage.unset(i, j);
                        Point point1 = new Point(i, j);
                        queue.offer(point1);
                    }
                }
            }
        }
        long end = System.currentTimeMillis();
        Log.d("gsl", "time diff = " + (end - start));
    }

}